#include "class_factory_impl.h"
#include "../../include/ibase_impl.h"
#include "../../include/ivariant_helper.h"
#include "../../include/ienum_helper.h"
#include "../../include/ref_obj_qi_ptr.h"
#include "../../include/iservice_locator.h"

#include <list>

#include <assert.h>


namespace Boss
{

  const double IClassFactoryImpl::ModuleLifeTime = 20;
  const ULong IClassFactoryImpl::CleanTimeout = 20000;

  TResult BOSS_CALL_TYPE IClassFactoryImpl::CreateObject(TGuid coClassId, TPtr *instance)
  {
    if (!coClassId || !*coClassId || !instance || *instance)
      return rcBadParam;
    ISyncObject Lock(GetSynObj());
    CoClassModulePool::const_iterator CoClassModuleIter = CoClassToModuleId.find(coClassId);
    if (CoClassModuleIter != CoClassToModuleId.end())
    {
      ModulePool::iterator ModuleIter = Modules.find(CoClassModuleIter->second);
      if (ModuleIter == Modules.end())
        return rcFail;
      try
      {
        RefObjPtr<IBase> Instance = ModuleIter->second->first->CreateObject(coClassId);
        ModuleIter->second->second = std::time(0);
        return Instance->QueryInterface(BOSS_UUID_OF(IBase), instance);
      }
      catch (std::exception &)
      {
      }
      return rcFail;
    }
    if (!Registry.Get())
      return rcFail;
    RefObjPtr<IVariant> ModuleId;
    if (Registry->GetModuleIdByCLSID(coClassId, ModuleId.GetPPtr()) != rcOk)
      return rcFail;
    try
    {
      std::string ModuleUUID = (const TGuid)IVariantHelper(ModuleId);
      RefObjPtr<IVariant> ModulePath;
      if (Registry->GetModulePath(ModuleUUID.c_str(), ModulePath.GetPPtr()) != rcOk)
        return rcFail;
      typedef std::list<std::string> StringListr;
      StringListr CoClassIds;
      {
        RefObjPtr<IEnum> ModuleCoClasses;
        if (Registry->GetModuleCoClasses(ModuleUUID.c_str(), ModuleCoClasses.GetPPtr()) != rcOk)
          return rcFail;
        IEnumHelper Enum(ModuleCoClasses);
        for (RefObjQIPtr<IVariant> i = Enum.First() ; i.Get() ; i = Enum.Next())
          CoClassIds.push_back((const std::string::value_type *)IVariantHelper(i));
      }
      SharedPtr<ModuleHolder> NewModule(new ModuleHolder(SharedPtr<DllHolder>(new DllHolder((const std::string::value_type *)IVariantHelper(ModulePath)))));
      NewModule->SetEnviroment(GetEnviroment());
      try
      {
        RefObjPtr<IBase> Instance = NewModule->CreateObject(coClassId);
        for (StringListr::const_iterator i = CoClassIds.begin() ; i != CoClassIds.end() ; ++i)
          CoClassToModuleId[*i] = ModuleUUID;
        Modules[ModuleUUID] = ModulePairPtr(new ModulePair(NewModule, std::time(0)));
        return Instance->QueryInterface(BOSS_UUID_OF(IBase), instance);
      }
      catch (...)
      {
        NewModule->SetEnviroment(RefObjPtr<IBase>(0));
        throw;
      }
    }
    catch (std::exception &)
    {
    }
    return rcFail;
  }

  TResult BOSS_CALL_TYPE IClassFactoryImpl::SetRegistry(IServiceRegistry *registry)
  {
    ISyncObject Lock(GetSynObj());
    Registry = registry;
    return rcOk;
  }

  bool IClassFactoryImpl::FinalizeCreate()
  {
    CleanerThread.reset(new PulsedThread(CreateMemberCallback(*this, &IClassFactoryImpl::Cleaner), CleanTimeout));
    return true;
  }

  void IClassFactoryImpl::BeforeDestroy()
  {
    ISyncObject Lock(GetSynObj());
    CleanerThread.reset(0);
    Registry.Release();
    CoClassToModuleId.clear();
    for (ModulePool::iterator i = Modules.begin() ; i != Modules.end() ; ++i)
    {
      try
      {
        assert(i->second->first->GetModuleCounter() == 0 && "Module has not zero counter");
      }
      catch (std::exception &)
      {
        assert(false && "Can't get module counter");
      }
      try
      {
        i->second->first->SetEnviroment(RefObjPtr<IBase>(0));
      }
      catch (std::exception &)
      {
        assert(false && "Can't set enviroment");
      }
    }
  }

  void IClassFactoryImpl::Cleaner()
  {
    ISyncObject Lock(GetSynObj());
    typedef std::list<std::string> StringPool;
    StringPool ModuleIdsForDel;
    for (ModulePool::iterator i = Modules.begin() ; i != Modules.end() ; ++i)
    {
      try
      {
        if (!i->second->first->GetModuleCounter() &&
          std::difftime(std::time(0), i->second->second) > ModuleLifeTime)
        {
          ModuleIdsForDel.push_back(i->first);
        }
        else
          i->second->second = std::time(0);
      }
      catch (std::exception &)
      {
        assert(false && "Can't get module counter");
      }
    }
    for (StringPool::const_iterator i = ModuleIdsForDel.begin() ; i != ModuleIdsForDel.end() ; ++i)
    {
      ModulePool::iterator Iter = Modules.find(*i);
      if (Iter == Modules.end())
      {
        assert(0 && "Module not found");
        continue;
      }
      try
      {
        ModuleHolder::GuidPool CoClassIds = Iter->second->first->GetCoClassIds();
        for (ModuleHolder::GuidPool::const_iterator j = CoClassIds.begin() ; j != CoClassIds.end() ; ++j)
          CoClassToModuleId.erase(*j);
      }
      catch (std::exception &)
      {
        assert("Can't get CoClassesIds");
      }
      try
      {
        Iter->second->first->SetEnviroment(RefObjPtr<IBase>(0));
      }
      catch (std::exception &)
      {
        assert(false && "Can't set enviroment");
      }
      Modules.erase(Iter);
    }
  }

}
